package com.fastframework.net {
	import com.fastframework.error.ErrorLog;
	import com.fastframework.event.EventDispatcherUtils;

	import flash.events.Event;
	import flash.utils.Dictionary;
	import flash.utils.setTimeout;

	/**
	 * @author colin
	 * ProxyCache act as proxy to remote data. 
	 * The properties only allow serialized strings.
	 * To use properties with other data type, parse properties after Event.COMPLETED
	 * This class caches loaded data.
	 * This class only use for static remote data only.
	 */
	final public class ProxyCache implements IProxy, IFASTEventDispatcher {
		public static const STRING_LOADING : String = 'loading...';
		public var props:Array=[];//<String>
		public var resolveRemoteXMLData:Function;
		private var pendingRequests:Dictionary=new Dictionary(true);//<String(props id):String(props name)>
		private var handlers:Dictionary=new Dictionary();//<String:[Function]>
		private var requestTimeoutId:int=-1;
		private var loadCompressXML:LoadXML;
		private var requestPendingTime:int;
		
		private var realObjectURL:String;
		private var id_prop_Map : Array;

		public function ProxyCache(realObjectURL:String,id_prop_Map:Array,requestPendingTime:int=500){
			this.resolveRemoteXMLData = _resolveRemoteXMLData;
			
			this.requestPendingTime = requestPendingTime;
			this.realObjectURL = realObjectURL;
			this.id_prop_Map = id_prop_Map;

			loadCompressXML = new LoadXML().when(Event.COMPLETE, this, doRequest2);
		}

		public function getValue(property:int,asyncHandler:Function):*{
			if(props[property]==null||props[property]==""){
				request(property);
				//resolve async Handler
				runHandler(asyncHandler,ProxyCache.STRING_LOADING);
				//pending request handlers managment
				var ary:Array = (handlers[property.toString()]||=[]);
				//check if the handler already assigned
				for each(var hnd:Function in ary){
					if(hnd===asyncHandler)return;
				}
					//add handler to pending request handlers
				ary.push(asyncHandler);
				return ProxyCache.STRING_LOADING;
			}
			//the property assigned, run handler immediately
			runHandler(asyncHandler,props[property]);
			return props[property];
			//handlers[property.toString()] = null;
		}

		private function request(type:int):void{
			pendingRequests[type.toString()] = id_prop_Map[type];
			if(requestTimeoutId==-1)requestTimeoutId=setTimeout(doRequest,requestPendingTime);
		}

		//doRequest collect pending requests by interval to reduce HTTP call
		private function doRequest():void{
			requestTimeoutId=-1;
			var url:String = realObjectURL;
			var requireFields:Array = [];
			var filterFields:Array=[];

			for(var id:String in pendingRequests){
				if(String(pendingRequests[id]).match("=")){
					filterFields.push(pendingRequests[id]);
					continue;
				}
				requireFields.push(pendingRequests[id]);
			}
			
			url+= filterFields.join("&")+((requireFields.length>0)?('&require_fields='+requireFields.join(",")):"");

			loadCompressXML.load(ResolveLink.create(url));
		}

		private function doRequest2(e:Event):void{
			var xData:XML = loadCompressXML.getBase();

			for(var req_id:String in handlers){
				if(resolveRemoteXMLData(req_id,xData)==false)continue;
				for each(var handler:Function in handlers[req_id]){
					runHandler(handler,props[req_id]);
				}
				delete handlers[req_id];
			}
			pendingRequests=new Dictionary();
		}

		private function runHandler(handler:Function,object:*):void{
			if(object is Array){
				handler('Array',object);
			}else{
				handler(object);
			}
		}

		private function _resolveRemoteXMLData(request_id:String, xmlData:XML):Boolean{
			//get the value from attributes
			props[request_id]=xmlData.attribute(id_prop_Map[request_id]).toString();
			//if it's blank, get the value from elements
			if(props[request_id]==""){
				props[request_id]=xmlData.elements(id_prop_Map[request_id]).toString();
			}
			
			//get the value from 1st child's first child
			if(props[request_id]==""){
				props[request_id]=xmlData.child(0).attribute(id_prop_Map[request_id]).toString();
			}
			//if it's blank, get the value from elements
			if(props[request_id]==""){
				props[request_id]=xmlData.child(0).elements(id_prop_Map[request_id]).toString();
			}
			
			//the property not exist or undefined
			if(props[request_id]==""){
				retry(new Error('ProxyCache::External XML not contains '+id_prop_Map[request_id]));
				return false;
			}
			return true;
		}
		
		public function retry(e:Error):void{
			ErrorLog.instance().log(e);
			if(requestTimeoutId==-1){
				requestTimeoutId=setTimeout(doRequest,Math.round(Math.random()*9000)+1000);
			}
		}
		
		public function when(eventType : String, whichObject : Object, callFunction : Function) : * {
			EventDispatcherUtils.when(loadCompressXML, eventType, whichObject, callFunction);
			return this;
		}
		
		public function dispatchEvent(event : Event) : Boolean {
			return loadCompressXML.dispatchEvent(event);
		}
		
		public function hasEventListener(type : String) : Boolean {
			return loadCompressXML.hasEventListener(type);
		}
		
		public function willTrigger(type : String) : Boolean {
			return loadCompressXML.willTrigger(type);
		}
		
		public function removeEventListener(type : String, listener : Function, useCapture : Boolean = false) : void {
			loadCompressXML.removeEventListener(type, listener,useCapture);
		}
		
		public function addEventListener(type : String, listener : Function, useCapture : Boolean = false, priority : int = 0, useWeakReference : Boolean = false) : void {
			loadCompressXML.addEventListener(type, listener,useCapture,priority,useWeakReference);
		}
	}
}
